// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 *   Copyright (C) 2011-2014, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 *******************************************************************************
 *   created on: 2011jan05
 *   created by: Markus W. Scherer
 *   ported from ICU4C bytestriebuilder.h/.cpp
 */

package com.ibm.icu.util;

import java.nio.ByteBuffer;

/**
 * Builder class for BytesTrie.
 *
 * <p>This class is not intended for public subclassing.
 *
 * @stable ICU 4.8
 * @author Markus W. Scherer
 */
public final class BytesTrieBuilder extends StringTrieBuilder {
    /**
     * Constructs an empty builder.
     *
     * @stable ICU 4.8
     */
    public BytesTrieBuilder() {}

    // Used in add() to wrap the bytes into a CharSequence for StringTrieBuilder.addImpl().
    private static final class BytesAsCharSequence implements CharSequence {
        public BytesAsCharSequence(byte[] sequence, int length) {
            s = sequence;
            len = length;
        }

        @Override
        public char charAt(int i) {
            return (char) (s[i] & 0xff);
        }

        @Override
        public int length() {
            return len;
        }

        @Override
        public CharSequence subSequence(int start, int end) {
            return null;
        }

        private byte[] s;
        private int len;
    }

    /**
     * Adds a (byte sequence, value) pair. The byte sequence must be unique. Bytes 0..length-1 will
     * be copied; the builder does not keep a reference to the input array.
     *
     * @param sequence The array that contains the byte sequence, starting at index 0.
     * @param length The length of the byte sequence.
     * @param value The value associated with this byte sequence.
     * @return this
     * @stable ICU 4.8
     */
    public BytesTrieBuilder add(byte[] sequence, int length, int value) {
        addImpl(new BytesAsCharSequence(sequence, length), value);
        return this;
    }

    /**
     * Builds a BytesTrie for the add()ed data. Once built, no further data can be add()ed until
     * clear() is called.
     *
     * <p>A BytesTrie cannot be empty. At least one (byte sequence, value) pair must have been
     * add()ed.
     *
     * <p>Multiple calls to build() or buildByteBuffer() return tries or buffers which share the
     * builder's byte array, without rebuilding. <em>The byte array must not be modified via the
     * buildByteBuffer() result object.</em> After clear() has been called, a new array will be
     * used.
     *
     * @param buildOption Build option, see StringTrieBuilder.Option.
     * @return A new BytesTrie for the add()ed data.
     * @stable ICU 4.8
     */
    public BytesTrie build(StringTrieBuilder.Option buildOption) {
        buildBytes(buildOption);
        return new BytesTrie(bytes, bytes.length - bytesLength);
    }

    /**
     * Builds a BytesTrie for the add()ed data and byte-serializes it. Once built, no further data
     * can be add()ed until clear() is called.
     *
     * <p>A BytesTrie cannot be empty. At least one (byte sequence, value) pair must have been
     * add()ed.
     *
     * <p>Multiple calls to build() or buildByteBuffer() return tries or buffers which share the
     * builder's byte array, without rebuilding. <em>Do not modify the bytes in the buffer!</em>
     * After clear() has been called, a new array will be used.
     *
     * <p>The serialized BytesTrie is accessible via the buffer's array()/arrayOffset()+position()
     * or remaining()/get(byte[]) etc.
     *
     * @param buildOption Build option, see StringTrieBuilder.Option.
     * @return A ByteBuffer with the byte-serialized BytesTrie for the add()ed data. The buffer is
     *     not read-only and array() can be called.
     * @stable ICU 4.8
     */
    public ByteBuffer buildByteBuffer(StringTrieBuilder.Option buildOption) {
        buildBytes(buildOption);
        return ByteBuffer.wrap(bytes, bytes.length - bytesLength, bytesLength);
    }

    private void buildBytes(StringTrieBuilder.Option buildOption) {
        // Create and byte-serialize the trie for the elements.
        if (bytes == null) {
            bytes = new byte[1024];
        }
        buildImpl(buildOption);
    }

    /**
     * Removes all (byte sequence, value) pairs. New data can then be add()ed and a new trie can be
     * built.
     *
     * @return this
     * @stable ICU 4.8
     */
    public BytesTrieBuilder clear() {
        clearImpl();
        bytes = null;
        bytesLength = 0;
        return this;
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected boolean matchNodesCanHaveValues() /*const*/ {
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int getMaxBranchLinearSubNodeLength() /*const*/ {
        return BytesTrie.kMaxBranchLinearSubNodeLength;
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int getMinLinearMatch() /*const*/ {
        return BytesTrie.kMinLinearMatch;
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int getMaxLinearMatchLength() /*const*/ {
        return BytesTrie.kMaxLinearMatchLength;
    }

    private void ensureCapacity(int length) {
        if (length > bytes.length) {
            int newCapacity = bytes.length;
            do {
                newCapacity *= 2;
            } while (newCapacity <= length);
            byte[] newBytes = new byte[newCapacity];
            System.arraycopy(
                    bytes,
                    bytes.length - bytesLength,
                    newBytes,
                    newBytes.length - bytesLength,
                    bytesLength);
            bytes = newBytes;
        }
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int write(int b) {
        int newLength = bytesLength + 1;
        ensureCapacity(newLength);
        bytesLength = newLength;
        bytes[bytes.length - bytesLength] = (byte) b;
        return bytesLength;
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int write(int offset, int length) {
        int newLength = bytesLength + length;
        ensureCapacity(newLength);
        bytesLength = newLength;
        int bytesOffset = bytes.length - bytesLength;
        while (length > 0) {
            bytes[bytesOffset++] = (byte) strings.charAt(offset++);
            --length;
        }
        return bytesLength;
    }

    private int write(byte[] b, int length) {
        int newLength = bytesLength + length;
        ensureCapacity(newLength);
        bytesLength = newLength;
        System.arraycopy(b, 0, bytes, bytes.length - bytesLength, length);
        return bytesLength;
    }

    // For writeValueAndFinal() and writeDeltaTo().
    private final byte[] intBytes = new byte[5];

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int writeValueAndFinal(int i, boolean isFinal) {
        if (0 <= i && i <= BytesTrie.kMaxOneByteValue) {
            return write(((BytesTrie.kMinOneByteValueLead + i) << 1) | (isFinal ? 1 : 0));
        }
        int length = 1;
        if (i < 0 || i > 0xffffff) {
            intBytes[0] = (byte) BytesTrie.kFiveByteValueLead;
            intBytes[1] = (byte) (i >> 24);
            intBytes[2] = (byte) (i >> 16);
            intBytes[3] = (byte) (i >> 8);
            intBytes[4] = (byte) i;
            length = 5;
            // } else if(i<=BytesTrie.kMaxOneByteValue) {
            //     intBytes[0]=(byte)(BytesTrie.kMinOneByteValueLead+i);
        } else {
            if (i <= BytesTrie.kMaxTwoByteValue) {
                intBytes[0] = (byte) (BytesTrie.kMinTwoByteValueLead + (i >> 8));
            } else {
                if (i <= BytesTrie.kMaxThreeByteValue) {
                    intBytes[0] = (byte) (BytesTrie.kMinThreeByteValueLead + (i >> 16));
                } else {
                    intBytes[0] = (byte) BytesTrie.kFourByteValueLead;
                    intBytes[1] = (byte) (i >> 16);
                    length = 2;
                }
                intBytes[length++] = (byte) (i >> 8);
            }
            intBytes[length++] = (byte) i;
        }
        intBytes[0] = (byte) ((intBytes[0] << 1) | (isFinal ? 1 : 0));
        return write(intBytes, length);
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int writeValueAndType(boolean hasValue, int value, int node) {
        int offset = write(node);
        if (hasValue) {
            offset = writeValueAndFinal(value, false);
        }
        return offset;
    }

    /**
     * {@inheritDoc}
     *
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    @Override
    protected int writeDeltaTo(int jumpTarget) {
        int i = bytesLength - jumpTarget;
        assert (i >= 0);
        if (i <= BytesTrie.kMaxOneByteDelta) {
            return write(i);
        } else {
            return write(intBytes, internalEncodeDelta(i, intBytes));
        }
    }

    /**
     * @internal
     * @deprecated This API is ICU internal only.
     */
    @Deprecated
    public static final int internalEncodeDelta(int i, byte[] intBytes) {
        assert (i >= 0);
        if (i <= BytesTrie.kMaxOneByteDelta) {
            intBytes[0] = (byte) i;
            return 1;
        }
        int length = 1;
        if (i <= BytesTrie.kMaxTwoByteDelta) {
            intBytes[0] = (byte) (BytesTrie.kMinTwoByteDeltaLead + (i >> 8));
        } else {
            if (i <= BytesTrie.kMaxThreeByteDelta) {
                intBytes[0] = (byte) (BytesTrie.kMinThreeByteDeltaLead + (i >> 16));
            } else {
                if (i <= 0xffffff) {
                    intBytes[0] = (byte) BytesTrie.kFourByteDeltaLead;
                } else {
                    intBytes[0] = (byte) BytesTrie.kFiveByteDeltaLead;
                    intBytes[1] = (byte) (i >> 24);
                    length = 2;
                }
                intBytes[length++] = (byte) (i >> 16);
            }
            intBytes[length++] = (byte) (i >> 8);
        }
        intBytes[length++] = (byte) i;
        return length;
    }

    // Byte serialization of the trie.
    // Grows from the back: bytesLength measures from the end of the buffer!
    private byte[] bytes;
    private int bytesLength;
}
